import sys
import socket
import argparse
import threading
import queue
import os
from datetime import datetime
from urllib.parse import urlparse
from packaging.version import parse as parse_version, InvalidVersion

# ANSI color codes
light_gray_color = '\033[37;1m'
dimmed_gray_color = '\033[90m'
honey_yellow_color = "\033[38;5;214m"
dim_yellow_color = "\033[33;1m"
cyan_color = '\033[96m'
green_color = '\033[92m'
dimmed_green_color = '\033[2;32m'
red_color = '\033[31m'
light_orange_color = '\033[38;5;214m'
reset_color = '\033[0m'

the_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# Log directory and file
LOG_DIR = 'logs'
LOG_FILE = os.path.join(LOG_DIR, 'scan.log')

def banner():
    print(f"""
{light_orange_color}
▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀▀█ ▒█▀▀▀█ ▒█░▒█ 　 ▒█▀▀▀█ █▀▀ █▀▀█ █▀▀▄ █▀▀▄ █▀▀ █▀▀█ 
▒█░░▒█ █░░█ █▀▀ █░░█ ░▀▀▀▄▄ ░▀▀▀▄▄ ▒█▀▀█ 　 ░▀▀▀▄▄ █░░ █▄▄█ █░░█ █░░█ █▀▀ █▄▄▀ 
▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█▄▄▄█ ▒█▄▄▄█ ▒█░▒█ 　 ▒█▄▄▄█ ▀▀▀ ▀░░▀ ▀░░▀ ▀░░▀ ▀▀▀ ▀░▀▀
  {reset_color}{light_gray_color}-> Bulk Scanning Tool for OpenSSH RCE CVE-2024-6387, CVE-2006-5051 and CVE-2008-4109.
{reset_color}
    """)

def is_vulnerable(version):
    if version.startswith("OpenSSH_"):
        version_num = version.split('_')[1].split()[0]  # Split out version number
        try:
            parsed_version = parse_version(version_num.replace("p", "."))
        except InvalidVersion:
            return False, None
        
        # Check if version is earlier than 4.4p1
        if parsed_version < parse_version("4.4"):
            return True, "CVE-2006-5051, CVE-2008-4109"
        # Check if version is in the range 8.5p1 to 9.7p1
        if parse_version("8.5") <= parsed_version < parse_version("9.8"):
            return True, "CVE-2024-6387"
    return False, None

vulnerable_ips = []

# Function to create log directory
def create_log_dir():
    if not os.path.exists(LOG_DIR):
        os.makedirs(LOG_DIR)
        print_message('info', f"Log directory created: {LOG_DIR}")

# Function to log messages
def log_message(message):
    with open(LOG_FILE, 'a') as log_file:
        log_file.write(f"{the_time} - {message}\n")

# Function to print messages with ANSI colors
def print_message(level, message):
    if level == 'vulnerable':
        print(f"[{light_gray_color}{the_time}] {light_orange_color}[VULN] {message}{reset_color}")
    if level == 'info':
        print(f"[{light_gray_color}{the_time}] {dimmed_gray_color}[INFO] {message}{reset_color}")
    elif level == 'ok':
        print(f"[{light_gray_color}{the_time}] {dimmed_green_color}[OK] {message}{reset_color}")
    elif level == 'warning':
        print(f"[{light_gray_color}{the_time}] {light_gray_color}[INFO] {message}{reset_color}")
    elif level == 'error':
        print(f"[{light_gray_color}{the_time}] {red_color}[ERROR] {message}{reset_color}")
    log_message(message)

# Function to get OpenSSH version
def get_ssh_version(ip, port):
    try:
        sock = socket.create_connection((ip, port), timeout=5)
        sock.sendall(b'\x00')
        response = sock.recv(1024).decode().strip()
        sock.close()
        if response.startswith("SSH-2.0-OpenSSH"):
            version_info = response.split('-')[2]
            if "OpenSSH_" in version_info:
                version = version_info.split('_')[1]
                return version
            else:
                return "no version"
        return "Invalid SSH identification string."
    except socket.error as e:
        return None

# Function to test a single host
def test_host(target):
    if "://" in target:
        target = target.split("://")[1]

    target = target.rstrip('/')

    if ":" in target:
        ip, port = target.split(":")
        try:
            port = int(port)
        except ValueError:
            print_message('error', f"Invalid port in target {target}")
            return
    else:
        ip = target
        port = 22

    version = get_ssh_version(ip, port)
    if version:
        message = f"OpenSSH version {version} {ip}:{port}"
        is_vuln, cve_number = is_vulnerable(f"OpenSSH_{version}")
        if is_vuln:
            vuln_message = f"{cve_number} OpenSSH version {version} {ip}:{port}"
            print_message('vulnerable', vuln_message)
            vulnerable_ips.append(target)
        elif version == "Invalid SSH identification string.":
            message = f"{version} {ip}:{port}"
            print_message('info', message)
        else:
            print_message('ok', message)
    else:
        print_message('info', f"No OpenSSH {ip}:{port}")

# Worker function for threading
def worker(queue):
    while not queue.empty():
        target = queue.get()
        test_host(target)
        queue.task_done()

# Main function
def main():
    banner()
    parser = argparse.ArgumentParser(description='OpenSSH Bulk Vesrioning Scanning Tool for CVE-2024-6387, CVE-2006-5051 and CVE-2008-4109.')
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-u', '--url', help='Target IP:PORT (e.g., 192.168.1.1:22)')
    group.add_argument('-f', '--file', help='File containing list of targets/IPs (one per line)')
    parser.add_argument('--output', help='File to save vulnerable IPs', required=False)

    args = parser.parse_args()

    create_log_dir()

    if not args.url and not args.file:
        parser.error("one of the arguments -u to scan a single IP or -f Bulk IPs file path is required")

    if args.url:
        test_host(args.url)
    elif args.file:
        try:
            with open(args.file, 'r') as f:
                targets = [line.strip() for line in f if line.strip()]
        except FileNotFoundError:
            print_message('error', f"File not found: {args.file}")
            sys.exit(1)

        target_queue = queue.Queue()
        for target in targets:
            target_queue.put(target)

        threads = []
        for _ in range(10):
            t = threading.Thread(target=worker, args=(target_queue,))
            t.start()
            threads.append(t)

        for t in threads:
            t.join()

    if args.output:
        with open(args.output, 'w') as output_file:
            for ip in vulnerable_ips:
                output_file.write(f"{ip}\n")

    print_message('info', "Scanning complete.")

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print_message('error', "Scanning interrupted by user.")
        sys.exit(1)
